package org.devastra.arcinspect.analyzer.parser;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.devastra.arcinspect.core.data.ClassInfo;
import org.devastra.arcinspect.core.data.EntityInfo;
import org.devastra.arcinspect.core.data.EntityType;
import org.devastra.arcinspect.core.data.EnumInfo;
import org.devastra.arcinspect.core.data.InterfaceInfo;
import org.devastra.arcinspect.core.data.PackageInfo;
import org.devastra.arcinspect.core.data.AbstractEntityInfo;
import org.devastra.arcinspect.core.data.UnknownEntityInfo;
import org.devastra.arcinspect.core.data.cache.EntityInfoCache;
import org.devastra.arcinspect.core.util.LogUtil;

public class FileParser {
	
	private static Pattern COMMENT_START_PATTERN = Pattern.compile("/\\*");
	private static Pattern COMMENT_END_PATTERN = Pattern.compile("\\*/");
	
	private static Pattern PACKAGE_DECLARATION_PATTERN = Pattern.compile("\\s*package\\s{1,}(\\S{1,})\\s{0,};");
	//private static Pattern CLASS_DECLARATION_PATTERN = Pattern.compile("public\\s{1,}clas{2}\\s{1,}(\\S{1,})\\s{0,}\\{");
	private static Pattern CLASS_DECLARATION_PATTERN = Pattern.compile("((public|protected)\\s{1,})?(abstract\\s{1,})?clas{2}\\s{1,}(\\S{1,})\\s{0,}");
	//private static Pattern ENUM_DECLARATION_PATTERN  = Pattern.compile("((public|protected)\\s{1,})?(abstract\\s{1,})?enum\\s{1,}(\\S{1,})\\s{0,}");
	private static Pattern ENUM_DECLARATION_PATTERN = Pattern.compile("public\\s{1,}enum\\s{1,}(\\S{1,})\\s{0,}");
	//private static Pattern INTERFACE_DECLARATION_PATTERN = Pattern.compile("public\\s{1,}interface\\s{1,}(\\S{1,})\\s{0,}\\{");
	private static Pattern INTERFACE_DECLARATION_PATTERN = Pattern.compile("public\\s{1,}interface\\s{1,}(\\S{1,})\\s{0,}");
	
	private static Pattern STANDARD_IMPORT_PATTERN = Pattern.compile("import\\s{1,}([a-zA-Z_0-9\\.]{1,})\\s{0,};");
	private static Pattern STATIC_IMPORT_PATTERN = Pattern.compile("import\\s{1,}static\\s{1,}([a-zA-Z_0-9\\.]{1,})\\s{0,};");
	private static Pattern GROUP_IMPORT_PATTERN = Pattern.compile("import\\s{1,}([a-zA-Z_0-9\\.]{1,})\\.\\*\\s{0,};");
	
	private static Pattern COMPLETE_STATEMENT_PATTERN = Pattern.compile("[;\\{\\}]\\s*$");
	
	/**
	 * reads and returns the packageInfo. This method is currently private because it 
	 * only works with cleaned code (optimization)
	 * 
	 * @param _cleanCode
	 * @return
	 */
	private static PackageInfo getPackageInfo(List<String> _cleanCode) {
		
		String pkgName = null;
		PackageInfo pkgInfo = null;
		
		for(String line : _cleanCode) {
			
			//look for package, class or interface declaration
			Matcher pkgMatcher = PACKAGE_DECLARATION_PATTERN.matcher(line);
			if(pkgMatcher.find()) {
				pkgName = pkgMatcher.group(1);
				break;
			}
		}
		
		//pkgInfo = (pkgName != null)? PackageInfo.getPackage(pkgName): null;
		if(pkgName != null) {
			pkgInfo = new PackageInfo(pkgName);
		} else {
			System.out.println("Error: Cannot get Package Info for : \n");
			for(String s : _cleanCode) {
				System.out.println("\t"+s);
			}
			System.exit(0);
		}
		return pkgInfo;
	}

	/**
	 * Returns the EntityInfo defined in the file
	 * @param _fileCode
	 * @return
	 */
	public static EntityInfo getEntityInfo(List<String> _fileCode) {
		
		EntityInfo entityInfo = null;
		EntityType entityType = null;
		String entityName = null;
		
		List<EntityInfo> refEntityInfoList = new ArrayList<EntityInfo>();
		
		List<String> cleanCode = cleanup(_fileCode);
		
		try {
			
			PackageInfo pkgInfo = getPackageInfo(cleanCode);
			
			for(String line : cleanCode) {
				
				Matcher stdImpMatcher = STANDARD_IMPORT_PATTERN.matcher(line);
				if(stdImpMatcher.find()) {
					String refEntityName = stdImpMatcher.group(1);
					EntityInfo refEntityInfo = EntityInfoCache.getEntityInfo(refEntityName);
					if(refEntityInfo == null) {
						refEntityInfo = new UnknownEntityInfo(refEntityName);
						//EntityInfoCache.registerEntity(refEntityInfo);
					}
					refEntityInfoList.add(refEntityInfo);
				}

				Matcher grpImpMatcher = GROUP_IMPORT_PATTERN.matcher(line);
				if(grpImpMatcher.find()) {
					String refPkgName = grpImpMatcher.group(1);
					refEntityInfoList.add(PackageInfo.getPackage(refPkgName));
				}

				Matcher staticImpMatcher = STATIC_IMPORT_PATTERN.matcher(line);
				if(staticImpMatcher.find()) {
					String refPkgName = staticImpMatcher.group(1);
					refEntityInfoList.add(PackageInfo.getPackage(refPkgName));
				}

				Matcher classMatcher = CLASS_DECLARATION_PATTERN.matcher(line);
				if(classMatcher.find()) {
					entityType = EntityType.CLASS;
					entityName = classMatcher.group(4);
					break;
				}
				
				Matcher enumMatcher = ENUM_DECLARATION_PATTERN.matcher(line);
				if(enumMatcher.find()) {
					entityType = EntityType.ENUM;
					entityName = enumMatcher.group(1);
					break;
				}
				
				Matcher intfMatcher = INTERFACE_DECLARATION_PATTERN.matcher(line);
				if(intfMatcher.find()) {
					entityType = EntityType.INTERFACE;
					entityName = intfMatcher.group(1);
					break;
				}
			}
			
			if(entityType == null) {
				RuntimeException re = new RuntimeException("Cannot determine Entity Type");
				System.out.println("Cannot determine Entity Type\n");
				for(String s : cleanCode) {
					System.out.println("\t"+s);
				}
				re.printStackTrace();
				//System.exit(0);
				return null;
			}
			
			switch(entityType) {
			case CLASS:
				entityInfo = EntityInfoCache.getEntityInfo(entityName);
				if(entityInfo == null) {
					//entityInfo = new ClassInfo(entityName, pkgInfo.getId());
					entityInfo = new ClassInfo(pkgInfo.getName(), entityName);
				} else if (EntityType.UNKNOWN.equals(entityInfo.getType())) {
					entityInfo = new ClassInfo(entityInfo);
				}
				
				//check for sanity
				if(!EntityType.CLASS.equals(entityInfo.getType())) {
					System.out.println("Error: Sanity Check Failed: "+entityInfo.getQualifiedName()+", type: "+entityInfo.getType());
				}
				break;
			case INTERFACE:
				entityInfo = EntityInfoCache.getEntityInfo(entityName);
				if(entityInfo == null) {
					entityInfo = new InterfaceInfo(pkgInfo.getName(), entityName);
				} else if (EntityType.UNKNOWN.equals(entityInfo.getType())) {
					entityInfo = new InterfaceInfo(entityInfo);
				}
				
				//check for sanity
				if(!EntityType.INTERFACE.equals(entityInfo.getType())) {
					System.out.println("Error: Sanity Check Failed: "+entityInfo.getQualifiedName()+", type: "+entityInfo.getType());
				}
				break;
			case ENUM:
				entityInfo = EntityInfoCache.getEntityInfo(entityName);
				if(entityInfo == null) {
					entityInfo = new EnumInfo(pkgInfo.getName(), entityName);
				} else if (EntityType.UNKNOWN.equals(entityInfo.getType())) {
					entityInfo = new EnumInfo(entityInfo);
				}
				
				//check for sanity
				if(!EntityType.ENUM.equals(entityInfo.getType())) {
					System.out.println("Error: Sanity Check Failed: "+entityInfo.getQualifiedName()+", type: "+entityInfo.getType());
				}
				break;
			default:
				entityInfo = null;
			}
			
			if(entityInfo != null) {
				for(EntityInfo refInfo : refEntityInfoList) {
					((AbstractEntityInfo) entityInfo).addReferencedEntityInfo(refInfo);
				}
			}
			
		} catch(Exception ioe) {
			LogUtil.printStackTrace(ioe);
			
			for(String s : _fileCode) {
				System.out.println("\t"+s);
			}
		}
		
		return entityInfo;
	}

	/**
	 * This method removes all comments and merges multiline into a single line
	 * @param _fileCode
	 * @return
	 */
	public static List<String> cleanup(List<String> _fileCode) {
		
		List<String> cleanCode = new ArrayList<String>();
		
		boolean inComment = false;
		
		try {
			String partialLine = null;
			
			for(String rawLine : _fileCode) {
				
				String line = null;
				
				if(!inComment) {
					Matcher m = COMMENT_START_PATTERN.matcher(rawLine);
					
					if(m.find()) {
						//find the code before the comment start
						
						inComment = true;
						continue;
					} else {
						line = rawLine.trim();
					}
				} else {
					Matcher m = COMMENT_END_PATTERN.matcher(rawLine);
					
					if(m.find()) {
						//find the code before the comment start
						
						inComment = false;
						line = rawLine.substring(m.end(), rawLine.length()).trim();
					}
				}
				
				if(line == null || line.length() == 0) {
					continue;
				}
				
				/*
				 * Ensure complete lines. If line does not match STATEMENT_COMPLETION_PATTERN,
				 * then hold on to this line and include the next line
				 * 
				 * Note on STATEMENT_COMPLETION_PATTERN;
				 * A line is considered complete, if it end with one of the following:
				 *   {, }, ;
				 */
				
				Matcher completeStmtMatcher = COMPLETE_STATEMENT_PATTERN.matcher(line);
				if(!completeStmtMatcher.find()) { 			//line completion does not match
					partialLine = (partialLine != null)? partialLine+' '+line: line;
					//System.out.println("partialLine="+partialLine);
					continue;
				}
				
				if(partialLine != null) {
					line = partialLine + ' ' + line;
					partialLine = null;
				}
				
				cleanCode.add(line);
			}
			
		} catch(Exception ioe) {
			LogUtil.printStackTrace(ioe);
		}
		
		return cleanCode;
	}

	/**
	 * Some small testing
	 * @param args
	 */
	public static void testCompleteStatementPattern(String[] args) {
		String[] lines = new String[] {
				"package foo.com.*;",
				"package foo.com.*; abd",
				"public class MyClass {  ",
				"public class MyClass {  int ",
		};
		
		for(String line : lines) {
			Matcher lineEndMatcher = COMPLETE_STATEMENT_PATTERN.matcher(line);
			if(!lineEndMatcher.find()) { 			//line completion does not match
				//partialLine = (partialLine != null)? partialLine+line: line;
				System.out.println("incomplete="+line);
				//continue;
			} else {
				System.out.println("complete.line="+line);
			}
		}
		
	}
	
	public static void main(String[] args) {
		String line = "import foo.bar.util.*;";
		
		Matcher m = GROUP_IMPORT_PATTERN.matcher(line);
		if(m.find()) {
			System.out.println("match found");
		} else {
			System.out.println("match not found");
		}
	}
}
